package guda.mvcx.core.validator;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * Created by well on 17/6/15.
 */
public class BeanValidator {

    private static ConcurrentHashMap<Class, List<FieldConstraint>> cache = new ConcurrentHashMap();

    /**
     * 校验bean
     * @param obj
     * @return
     */
    public static ValidResult valid(Object obj) {
        ValidResult validResult = new ValidResult();
        if (obj == null) {
            Map<String, String> errorMap = new HashMap<>();
            errorMap.put("target", ValidErrorMsg.TARGET_IS_NULL);
            validResult.setErrorInfo(errorMap);
            return validResult;
        }
        Map<String, String> errorMap = new HashMap<>();
        validInnerObj(obj, errorMap);
        validResult.setErrorInfo(errorMap);
        return validResult;
    }

    private static void validInnerObj(Object obj, Map<String, String> errorMap) {
        Class cls = obj.getClass();
        try {
            List<FieldConstraint> fieldConstraint = getFieldConstraint(cls);
            Iterator<FieldConstraint> iterator = fieldConstraint.iterator();
            while (iterator.hasNext()) {
                FieldConstraint next = iterator.next();
                Class fieldClass = next.getFieldClass();
                ValidType[] validTypeArray = next.getValidTypeArray();
                Object fieldVal = next.getReadMethod().invoke(obj);
                if (!isSimpleClass(fieldClass) && fieldVal != null) {
                    validInnerObj(fieldVal, errorMap);
                }
                for (ValidType validType : validTypeArray) {
                    switch (validType) {
                        case NotNull:
                            checkNotNull(fieldVal, errorMap, next);
                            break;
                        case NotEmpty:
                            checkNotEmpty(fieldVal, errorMap, next);
                            break;
                        case Length:
                            checkLength(fieldVal, errorMap, next);
                            break;
                        case Size:
                            checkSize(fieldVal, errorMap, next);
                            break;
                        case Regex:
                            checkRegex(fieldVal, errorMap, next);
                            break;
                        default:
                            errorMap.put(next.getFieldName(), ValidErrorMsg.UNKOWN_VALID_TYPE);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("valid error", e);
        }
    }

    private static List<FieldConstraint> getFieldConstraint(Class clzz) throws IntrospectionException {
        List<FieldConstraint> stringFieldConstraint = cache.get(clzz);
        if (stringFieldConstraint != null) {
            return stringFieldConstraint;
        }
        List<FieldConstraint> list = new ArrayList<>();
        parseSuperConstraint(clzz, list);
        if (list.size() == 0) {
            cache.put(clzz, Collections.EMPTY_LIST);
        } else {
            cache.put(clzz, list);
        }
        return list;
    }

    private static void parseSuperConstraint(Class clazz, List<FieldConstraint> fieldConstraintList) throws IntrospectionException {
        if (clazz == null || fieldConstraintList == null) {
            return;
        }
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            Valid annotation = field.getAnnotation(Valid.class);
            if (annotation == null) {
                continue;
            }
            FieldConstraint fieldConstraint = new FieldConstraint();
            fieldConstraint.setValidTypeArray(annotation.type());
            fieldConstraint.setFieldName(field.getName());
            fieldConstraint.setMaxLength(annotation.maxLength());
            fieldConstraint.setMaxSize(annotation.maxSize());
            fieldConstraint.setMinLength(annotation.minLength());
            fieldConstraint.setMinSize(annotation.minSize());
            fieldConstraint.setMsg(annotation.msg());
            fieldConstraint.setRegex(annotation.regex());

            PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
            Method getMethod = pd.getReadMethod();
            fieldConstraint.setReadMethod(getMethod);
            fieldConstraint.setFieldClass(field.getType());
            if (!isSimpleClass(field.getType())) {
                parseChildConstraint(field.getType(), fieldConstraint);
            }

            fieldConstraintList.add(fieldConstraint);
        }
        parseSuperConstraint(clazz.getSuperclass(), fieldConstraintList);
    }

    private static void parseChildConstraint(Class clzz, FieldConstraint parent) throws IntrospectionException {
        List<FieldConstraint> stringFieldConstraint = cache.get(clzz);
        if (stringFieldConstraint != null) {
            return;
        }
        List<FieldConstraint> list = new ArrayList<>();
        parseSuperConstraint(clzz, list);
        if (list.size() == 0) {
            cache.put(clzz, Collections.EMPTY_LIST);
        } else {
            list.forEach(cons->{
                cons.setParent(parent);
            });
            cache.put(clzz, list);
        }

    }

    private static void checkNotNull(Object value, Map<String, String> error, FieldConstraint fieldConstraint) {
        if (value == null) {
            fillErrorMsg(fieldConstraint, error, ValidErrorMsg.NOT_NULL_MSG);
            return;
        }
    }

    private static void checkNotEmpty(Object value, Map<String, String> error, FieldConstraint fieldConstraint) {
        if (value == null) {
            fillErrorMsg(fieldConstraint, error, ValidErrorMsg.NOT_EMPTY_MSG);
            return;
        }
        if (value.getClass() == String.class) {
            if (String.valueOf(value).trim().length() == 0) {
                fillErrorMsg(fieldConstraint, error, ValidErrorMsg.NOT_EMPTY_MSG);
            }
            return;
        }
        if (isCollectinClass(value.getClass())) {
            if (value instanceof Collection) {
                Collection collection = (Collection) value;
                if (collection.size() == 0) {
                    fillErrorMsg(fieldConstraint, error, ValidErrorMsg.NOT_EMPTY_MSG);
                }
            }
            if (value instanceof Map) {
                Map map = (Map) value;
                if (map.size() == 0) {
                    fillErrorMsg(fieldConstraint, error, ValidErrorMsg.NOT_EMPTY_MSG);
                }
            }
            return;
        }
        throw new RuntimeException(ValidErrorMsg.VALID_NOT_EMPTY_TYPE_ERROR);
    }

    private static void fillErrorMsg(FieldConstraint fieldConstraint, Map<String, String> error, String newMsg) {
        if (fieldConstraint.getMsg() != null && fieldConstraint.getMsg().length() > 0) {
            error.put(renderFieldShowName(fieldConstraint), fieldConstraint.getMsg());
        } else {
            error.put(renderFieldShowName(fieldConstraint), newMsg);
        }
    }

    private static String renderFieldShowName(FieldConstraint fieldConstraint) {
        if (fieldConstraint == null) {
            return null;
        }
        List<String> fieldList = new ArrayList<>();
        renderFieldName(fieldConstraint, fieldList);
        if (fieldList.size() == 1) {
            return fieldList.get(0);
        }
        Collections.reverse(fieldList);
        StringBuilder buf = new StringBuilder();
        for (String s : fieldList) {
            buf.append(s).append(".");
        }
        return buf.substring(0, buf.length() - 1);
    }

    private static void renderFieldName(FieldConstraint fieldConstraint, List<String> fieldNameList) {
        fieldNameList.add(fieldConstraint.getFieldName());
        if (fieldConstraint.getParent() != null) {
            renderFieldName(fieldConstraint.getParent(), fieldNameList);
        }
    }

    private static void checkLength(Object value, Map<String, String> error, FieldConstraint fieldConstraint) {
        if (fieldConstraint.getMinLength() < 0 || fieldConstraint.getMaxLength() < 0) {
            throw new RuntimeException(ValidErrorMsg.VALID_LENGTH_LESS);
        }
        if (fieldConstraint.getMinLength() > fieldConstraint.getMaxLength()) {
            throw new RuntimeException(ValidErrorMsg.VALID_LENGTH_ERROR);
        }
        if (value == null) {
            if (fieldConstraint.getMinLength() > 0) {
                fillErrorMsg(fieldConstraint, error, ValidErrorMsg.LENGTH_ERROR_MSG + ",长度应该在" + fieldConstraint.getMinLength() + "和" + fieldConstraint.getMaxLength() + "之间");
            }
            return;
        }
        if (value.getClass() != String.class) {
            throw new RuntimeException(ValidErrorMsg.VALID_LENGTH_TYPE_ERROR);
        }
        int len = String.valueOf(value).length();
        if (len < fieldConstraint.getMinLength() || len > fieldConstraint.getMaxLength()) {
            fillErrorMsg(fieldConstraint, error, ValidErrorMsg.LENGTH_ERROR_MSG + ",长度应该在" + fieldConstraint.getMinLength() + "和" + fieldConstraint.getMaxLength() + "之间");
            return;
        }
    }

    private static void checkSize(Object value, Map<String, String> error, FieldConstraint fieldConstraint) {
        if (fieldConstraint.getMinSize() < 0 || fieldConstraint.getMaxSize() < 0) {
            throw new RuntimeException(ValidErrorMsg.VALID_SIZE_LESS);
        }
        if (fieldConstraint.getMinSize() > fieldConstraint.getMaxSize()) {
            throw new RuntimeException(ValidErrorMsg.VALID_SIZE_ERROR);
        }
        if (value == null && fieldConstraint.getMinSize() > 0) {
            fillErrorMsg(fieldConstraint, error, ValidErrorMsg.SIZE_ERROR_MSG + ",取值范围应该在" + fieldConstraint.getMinSize() + "和" + fieldConstraint.getMaxSize() + "之间");
            return;

        }
        if (value.getClass() == Integer.class || value.getClass() == int.class) {
            int i = Integer.parseInt(String.valueOf(value));
            if (i < fieldConstraint.getMinSize() || i > fieldConstraint.getMaxSize()) {
                fillErrorMsg(fieldConstraint, error, ValidErrorMsg.SIZE_ERROR_MSG + ",取值范围应该在" + fieldConstraint.getMinSize() + "和" + fieldConstraint.getMaxSize() + "之间");
            }
            return;
        }
        if (value.getClass() == Long.class || value.getClass() == long.class) {
            long i = Long.parseLong(String.valueOf(value));
            if (i < fieldConstraint.getMinSize() || i > fieldConstraint.getMaxSize()) {
                fillErrorMsg(fieldConstraint, error, ValidErrorMsg.SIZE_ERROR_MSG + ",取值范围应该在" + fieldConstraint.getMinSize() + "和" + fieldConstraint.getMaxSize() + "之间");
            }
            return;
        }
        if (Collection.class.isAssignableFrom(value.getClass())) {
            Collection collection = (Collection) value;
            if (collection.size() < fieldConstraint.getMinSize() || collection.size() > fieldConstraint.getMaxSize()) {
                fillErrorMsg(fieldConstraint, error, ValidErrorMsg.SIZE_ERROR_MSG + ",取值范围应该在" + fieldConstraint.getMinSize() + "和" + fieldConstraint.getMaxSize() + "之间");
            }
            return;
        }
        if (Map.class.isAssignableFrom(value.getClass())) {
            Map map = (Map) value;
            if (map.size() < fieldConstraint.getMinSize() || map.size() > fieldConstraint.getMaxSize()) {
                fillErrorMsg(fieldConstraint, error, ValidErrorMsg.SIZE_ERROR_MSG + ",取值范围应该在" + fieldConstraint.getMinSize() + "和" + fieldConstraint.getMaxSize() + "之间");
            }
        }
        throw new RuntimeException(ValidErrorMsg.VALID_SIZE_TYPE_ERROR);
    }

    private static void checkRegex(Object value, Map<String, String> error, FieldConstraint fieldConstraint) {
        if (fieldConstraint.getRegex() == null) {
            throw new RuntimeException(ValidErrorMsg.VALID_REGEX_ERROR);
        }
        if (value == null) {
            fillErrorMsg(fieldConstraint, error, ValidErrorMsg.VALID_REGEX_VALUE_NULL);
            return;
        }
        if (value.getClass() != String.class) {
            throw new RuntimeException(ValidErrorMsg.VALID_REGEX_TYPE_ERROR);
        }
        Pattern compile = Pattern.compile(fieldConstraint.getRegex());
        if (!compile.matcher(String.valueOf(value)).find()) {
            fillErrorMsg(fieldConstraint, error, ValidErrorMsg.REGEX_ERROR_MSG + ",不符合正则表达式:" + fieldConstraint.getRegex());
            return;
        }
    }

    public static boolean isSimpleClass(Class clazz) {
        if (clazz == null) {
            return false;
        }
        if (Integer.class.isAssignableFrom(clazz)
                || int.class.isAssignableFrom(clazz)
                || Long.class.isAssignableFrom(clazz)
                || long.class.isAssignableFrom(clazz)
                || Number.class.isAssignableFrom(clazz)
                || Float.class.isAssignableFrom(clazz)
                || float.class.isAssignableFrom(clazz)
                || Double.class.isAssignableFrom(clazz)
                || double.class.isAssignableFrom(clazz)
                || Character.class.isAssignableFrom(clazz)
                || char.class.isAssignableFrom(clazz)
                || Short.class.isAssignableFrom(clazz)
                || short.class.isAssignableFrom(clazz)
                || String.class.isAssignableFrom(clazz)
                || boolean.class.isAssignableFrom(clazz)
                || Boolean.class.isAssignableFrom(clazz)
                || Date.class.isAssignableFrom(clazz)) {
            return true;
        }
        return false;
    }

    public static boolean isCollectinClass(Class clazz) {
        if (clazz == null) {
            return false;
        }
        if (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)) {
            return true;
        }
        return false;
    }


}
